package com.captjack.distributedlock.lock.redis;

import com.captjack.distributedlock.lock.RedisDistributedLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;

import java.io.InputStream;
import java.util.concurrent.TimeUnit;

import static com.captjack.distributedlock.lock.redis.RedisDistributedLockConstant.UTF8_CHARSET;

/**
 * redis实现分布式锁
 *
 * @author Jack Sparrow
 * @version 1.0.0
 * @date 2018/7/12 21:54
 * package com.captjack.distributedlock.lock.redis
 */
public class RedisDistributedLockImpl implements RedisDistributedLock {

    /**
     * logger
     */
    private static final Logger logger = LoggerFactory.getLogger(RedisDistributedLockImpl.class);

    /**
     * redis操作
     */
    private final RedisTemplate redisTemplate;

    /**
     * 删除脚本的id
     */
    private String deleteScriptSha1Code;

    /**
     * 尝试加锁，如果key不存在那么set一个带有时效的key并且返回true，如果key存在返回false
     */
    @Override
    public boolean acquire(String lockId, String requestId, long lockExpireTime, TimeUnit lockExpireTimeUnit, boolean isExpireLock) {
        return (boolean) this.redisTemplate.execute((RedisCallback) redisConnection ->
                redisConnection.set(
                        lockId.getBytes(UTF8_CHARSET),
                        requestId.getBytes(UTF8_CHARSET),
                        Expiration.from(isExpireLock ? lockExpireTime : -1L, lockExpireTimeUnit),
                        RedisStringCommands.SetOption.SET_IF_ABSENT
                ));
    }

    @Override
    public boolean release(String lockId, String requestId) {
        // 获取返回状态值，返回-1
        long status = (long) this.redisTemplate.execute((RedisCallback) redisConnection ->
                redisConnection.evalSha(
                        deleteScriptSha1Code,
                        ReturnType.INTEGER,
                        1,
                        lockId.getBytes(UTF8_CHARSET),
                        requestId.getBytes(UTF8_CHARSET)
                ));
        // 只要返回值不为0，就代表释放成功或不需要释放
        return status != 0L;
    }

    /**
     * 初始化redis脚本
     */
    private void init() {
        try {
            InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("luascript/deleteRedisKey.lua");
            // 脚本字节数组
            byte[] scriptBytes = new byte[inputStream.available()];
            // 一次性读取所有内容
            int length = inputStream.read(scriptBytes);
            logger.info("read script success! length: " + length);
            // 将脚本加入到redis缓存中，避免每次执行都需要重新编译
            String sha1 = (String) this.redisTemplate.execute((RedisCallback) redisConnection -> redisConnection.scriptingCommands().scriptLoad(scriptBytes));
            if (sha1 != null) {
                this.deleteScriptSha1Code = sha1;
                logger.info("init redis script success! sha1: " + sha1);
            } else {
                logger.error("init redis script failed!");
            }
        } catch (Exception e) {
            logger.error("RedisDistributedLockImpl init error!");
        }
    }

    /**
     * @param redisTemplate redis操作
     */
    public RedisDistributedLockImpl(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
        this.init();
    }

}
